/*
 * Atari DOS functions - Eric BACHER
 *
 * this file is in standard C.
 * It can be compiled without including windows.h for a DOS program.
 */

#include <stdio.h>
#include <string.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <ctype.h>
#include "atari.h"

static AtariError AtariSeek(FILE *fd, long lSector)
{
struct _stat buf;

     /*
      * determine if we have an XFD or ATR file
      */
     if (_fstat(fileno(fd), &buf))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * go to sector
      */
     if (buf.st_size == 92160L || buf.st_size == 133120L)
          {
          if (fseek(fd, lSector * 128L, SEEK_SET))
               return ATARI_ERR_SECTOR_NOT_FOUND;
          }
     else if (buf.st_size == 92176L || buf.st_size == 133136L)
          {
          if (fseek(fd, (lSector * 128L) + 16L, SEEK_SET))
               return ATARI_ERR_SECTOR_NOT_FOUND;
          }
     else return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * everything is OK.
      */
     return ATARI_ERR_OK;
}

static AtariError AtariFindDirEntry(FILE *fd, AtariFile *Info)
{
long lSector, lFirstSector;
unsigned wEntry, wFirstEntry;
unsigned wLng;
unsigned char sector[128];

     /*
      * go to the next entry in the directory.
      */
     Info->lIndex++;
     if (Info->lIndex < 0 || Info->lIndex > 63)
          return ATARI_ERR_NO_ENTRY_FOUND;

     /*
      * scan all directory sectors to find the first valid entry.
      */
     lFirstSector = 360 + (Info->lIndex / 8);
     for (lSector = lFirstSector; lSector < 368; lSector++)
          {

          /*
           * go to the directory sector.
           */
          if (AtariSeek(fd, lSector))
               return ATARI_ERR_DIRECTORY_NOT_FOUND;

          /*
           * the sector is read.
           */
          if (fread(sector, 128, 1, fd) != 1)
               return ATARI_ERR_DIRECTORY_READ;

          /*
           * scan all entries of this directory sector (8 entries).
           */
          if (lSector == lFirstSector)
               wFirstEntry = (unsigned) Info->lIndex % 8;
          else wFirstEntry = 0;
          for (wEntry = wFirstEntry; wEntry < 8; wEntry++)
               {
     
               /*
                * the file status is checked to determine if the file is
                * valid (not deleted).
                */
               if ((sector[(wEntry * 16)]) && ((sector[(wEntry * 16)] & ATARI_ATTR_DELETED) == 0))
                    {
     
                    /*
                     * the file name is copied to the structure.
                     */
                    memset(Info, 0, sizeof(AtariFile));
                    for (wLng = 0; wLng < 8; wLng++)
                         {
                         if (sector[(wEntry * 16) + 5 + wLng] != ' ')
                              Info->szName[wLng] = sector[(wEntry * 16) + 5 + wLng];
                         else break;
                         }
                    strcat(Info->szName, ".");
                    for (wLng = 0; wLng < 3; wLng++)
                         {
                         if (sector[(wEntry * 16) + 13 + wLng] != ' ')
                              Info->szName[strlen(Info->szName)] = sector[(wEntry * 16) + 13 + wLng];
                         else break;
                         }

                    /*
                     * get the file size in number of sectors and keep the
                     * index of the file entry so AtariFileNext knows where
                     * to start.
                     */
                    Info->lIndex = ((lSector - 360) * 8) + wEntry;
                    Info->wAttrib = sector[(wEntry * 16)];
                    Info->wSize = (unsigned) (((long) (sector[(wEntry * 16) + 2] * 256)) +
                                  (long) (sector[(wEntry * 16) + 1]));
                    Info->wStart = (unsigned) (((long) (sector[(wEntry * 16) + 4] * 256)) +
                                   (long) (sector[(wEntry * 16) + 3]));

                    /*
                     * return success.
                     */
                    return ATARI_ERR_OK;
                    }
               }
          }

     /*
      * return failure: no file found.
      */
     return ATARI_ERR_NO_ENTRY_FOUND;
}

static AtariError AtariGetFreeDirEntry(FILE *fd, long *lIndex)
{
long lSector;
unsigned wEntry;
unsigned char sector[128];

     /*
      * scan all directory sectors to find the first free entry.
      */
     for (lSector = 360; lSector < 368; lSector++)
          {

          /*
           * go to the directory sector.
           */
          if (AtariSeek(fd, lSector))
               return ATARI_ERR_DIRECTORY_NOT_FOUND;

          /*
           * the sector is read.
           */
          if (fread(sector, 128, 1, fd) != 1)
               return ATARI_ERR_DIRECTORY_READ;

          /*
           * scan all entries of this directory sector (8 entries).
           */
          for (wEntry = 0; wEntry < 8; wEntry++)
     
               /*
                * the file status is checked to determine if the entry is
                * free.
                */
               if ((sector[(wEntry * 16)] & ATARI_ATTR_IN_USE) == 0)
                    {

                    /*
                     * return the index found.
                     */
                    *lIndex = (long) (((lSector - 360) * 8) + wEntry);
                    return ATARI_ERR_OK;
                    }
          }

     /*
      * return failure: no file found.
      */
     return ATARI_ERR_NO_ENTRY_FOUND;
}

static AtariError AtariWriteDirEntry(FILE *fd, AtariFile *Info)
{
long lSector;
unsigned wEntry;
unsigned wLng, wExt;
unsigned char sector[128];

     /*
      * find directory sector.
      */
     lSector = 360 + (Info->lIndex / 8);

     /*
      * go to the directory sector.
      */
     if (AtariSeek(fd, lSector))
          return ATARI_ERR_DIRECTORY_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fread(sector, 128, 1, fd) != 1)
          return ATARI_ERR_DIRECTORY_READ;

     /*
      * find directory entry in the sector.
      */
     wEntry = (unsigned) Info->lIndex % 8;
     
     /*
      * write entry.
      */
     sector[(wEntry * 16)] = (unsigned char) Info->wAttrib;
     sector[(wEntry * 16) + 1] = (unsigned char) Info->wSize & 0xFF;
     sector[(wEntry * 16) + 2] = (unsigned char) (Info->wSize >> 8) & 0xFF;
     sector[(wEntry * 16) + 3] = (unsigned char) Info->wStart & 0xFF;
     sector[(wEntry * 16) + 4] = (unsigned char) (Info->wStart >> 8) & 0xFF;
     memset(&(sector[(wEntry * 16) + 5]), ' ', 11);
     for (wLng = 0; (wLng < 8) && (Info->szName[wLng]) && (Info->szName[wLng] != '.'); wLng++)
          sector[(wEntry * 16) + 5 + wLng] = toupper(Info->szName[wLng]);
     if (Info->szName[wLng] == '.')
          wLng++;
     for (wExt = 0; (wExt < 3) && (Info->szName[wLng]); wExt++, wLng++)
          sector[(wEntry * 16) + 13 + wExt] = toupper(Info->szName[wLng]);

     /*
      * go to the directory sector.
      */
     if (AtariSeek(fd, lSector))
          return ATARI_ERR_DIRECTORY_NOT_FOUND;

     /*
      * the sector is written.
      */
     if (fwrite(sector, 128, 1, fd) != 1)
          return ATARI_ERR_DIRECTORY_WRITE;
     return ATARI_ERR_OK;
}

static unsigned AtariAllocBit(unsigned char *byte)
{
unsigned wIndex;
unsigned wMask = 0x80;

     for (wIndex = 0; wIndex < 8; wIndex++)
          {
          if (*byte & wMask)
               {
               *byte &= ~wMask;
               return wIndex;
               }
          wMask >>= 1;
          }
     return 0;
}

static AtariError AtariAllocSector(FILE *fd, unsigned *wSector, long lIndex)
{
unsigned wIndex;
unsigned char sector[128];
unsigned char data[128];

     /*
      * go to sector.
      */
     if (AtariSeek(fd, 359L))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fread(sector, 128, 1, fd) != 1)
          return ATARI_ERR_FILE_READ;

     /*
      * check if there is a free sector.
      */
     if ((sector[3] == 0) && (sector[4] == 0))
          return ATARI_ERR_NO_FREE_SECTOR;

     /*
      * find a free sector.
      */
     for (wIndex = 10; wIndex <= 125; wIndex++)
          if (sector[wIndex])
               {

               /*
                * return the sector number
                */
               *wSector = ((wIndex - 10) * 8) + AtariAllocBit(&sector[wIndex]);

               /*
                * write a blank sector with the directory entry number filled.
                */
               memset(data, 0, sizeof(data));
               data[125] = ((unsigned char) lIndex & 0x3F) << 2;

               /*
                * go to sector.
                */
               if (AtariSeek(fd, *wSector - 1))
                    return ATARI_ERR_SECTOR_NOT_FOUND;

               /*
                * the sector is written.
                */
               if (fwrite(data, 128, 1, fd) != 1)
                    return ATARI_ERR_BITMAP_READ;

               /*
                * decrement the free sector count.
                */
               if (sector[3])
                    sector[3] = sector[3] - 1;
               else {
                    sector[3] = 0xFF;
                    sector[4] = sector[4] - 1;
                    }

               /*
                * go to sector.
                */
               if (AtariSeek(fd, 359L))
                    return ATARI_ERR_SECTOR_NOT_FOUND;

               /*
                * the sector is written.
                */
               if (fwrite(sector, 128, 1, fd) != 1)
                    return ATARI_ERR_BITMAP_WRITE;
               return ATARI_ERR_OK;
               }
     return ATARI_ERR_NO_FREE_SECTOR;
}

static AtariError AtariFreeSector(FILE *fd, unsigned wSector)
{
unsigned wIndex, wBit, wMask;
unsigned char sector[128];

     /*
      * go to sector.
      */
     if (AtariSeek(fd, 359L))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fread(sector, 128, 1, fd) != 1)
          return ATARI_ERR_BITMAP_READ;

     /*
      * determine index into sector and bit number
      */
     wIndex = ((wSector - 1) / 8) + 10;
     wBit = (wSector - 1) % 8;
     wMask = 0x80 >> wBit;

     /*
      * check if the sector is in use.
      */
     if (sector[wIndex] & wMask)
          return ATARI_ERR_SECTOR_ALREADY_FREE;

     /*
      * turn bit on.
      */
     sector[wIndex] |= wMask;

     /*
      * increment the free sector count.
      */
     if (sector[3] != 0xFF)
          sector[3] = sector[3] + 1;
     else {
          sector[3] = 0;
          sector[4] = sector[4] + 1;
          }

     /*
      * go to sector.
      */
     if (AtariSeek(fd, 359L))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fwrite(sector, 128, 1, fd) != 1)
          return ATARI_ERR_BITMAP_WRITE;
     return ATARI_ERR_OK;
}

static AtariError AtariSeekEndOfFile(FILE *fd, AtariFile *Info)
{
unsigned wSector;
long lIndex;
AtariError iRet;
unsigned char sector[128];

     /*
      * go to sector.
      */
     if (AtariSeek(fd, Info->wNextSector - 1))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fread(sector, 128, 1, fd) != 1)
          return ATARI_ERR_FILE_SEEK;

     /*
      * if the sector is empty, we write this one.
      * Otherwise, we must allocate a new one.
      */
     if (sector[127] & 0x7F)
          {
          if ((iRet = AtariAllocSector(fd, &wSector, lIndex)) != ATARI_ERR_OK)
               return iRet;

          /*
           * the last sector must be linked with the new one.
           */
          sector[125] &= ~0x03;
          sector[125] |= (wSector >> 8) & 0x03;
          sector[126] = wSector & 0xFF;
          if (AtariSeek(fd, Info->wNextSector - 1))
               return ATARI_ERR_SECTOR_NOT_FOUND;

          /*
           * the sector is written.
           */
          if (fwrite(sector, 128, 1, fd) != 1)
               return ATARI_ERR_FILE_WRITE;

          /*
           * tell the caller which is the last sector.
           */
          Info->wNextSector = wSector;

          /*
           * increment the file size in the directory.
           */
          Info->wSize++;
          if ((iRet = AtariWriteDirEntry(fd, Info)) != ATARI_ERR_OK)
               return iRet;
          }
     return ATARI_ERR_OK;
}

static AtariError AtariReadSector(FILE *fd, AtariFile *Info)
{
long lIndex;

     /*
      * check for end of file.
      */
     Info->wSectorLng = 0;
     if (Info->wNextSector == 0)
          return ATARI_ERR_END_OF_FILE;

     /*
      * go to sector.
      */
     if (AtariSeek(fd, Info->wNextSector - 1))
          return ATARI_ERR_SECTOR_NOT_FOUND;

     /*
      * the sector is read.
      */
     if (fread(Info->szSector, 128, 1, fd) != 1)
          return ATARI_ERR_FILE_READ;

     /*
      * check if the sector belongs to the file
      */
     lIndex = (long) ((Info->szSector[125] >> 2) & 0x3F);
     if (lIndex != Info->lIndex)
          return ATARI_ERR_FILE_CORRUPTED;

     /*
      * determination de la taille des données, du secteur suivant et
      * ecriture du secteur dans le fichier PC destination.
      */
     Info->wSectorLng = (unsigned) (Info->szSector[127] & 0x7F);
     if (((unsigned char) Info->szSector[127]) & 0x80)
          Info->wNextSector = 0;
     else Info->wNextSector = (unsigned) ((Info->szSector[125] & 0x03) << 8) +
                              (unsigned) (unsigned char) Info->szSector[126];
     return ATARI_ERR_OK;
}

/*
 * fill the structure with the first entry of the directory.
 * Files that have been deleted or open for output are skipped.
 */
AtariError AtariFindFirst(const char *szDisk, AtariFile *Info)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "rb")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * search from the first entry.
      */
     Info->lIndex = -1;

     /*
      * fill Info structure.
      */
     iRet = AtariFindDirEntry(fd, Info);
     fclose(fd);
     return iRet;
}

/*
 * fill the structure with the next entry of the directory.
 * Files that have been deleted or open for output are skipped.
 */
AtariError AtariFindNext(const char *szDisk, AtariFile *Info)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "rb")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * fill Info structure.
      */
     iRet = AtariFindDirEntry(fd, Info);
     fclose(fd);
     return iRet;
}

/*
 * fill the structure with the entry at the given index.
 * Files that have been deleted or open for output are skipped.
 * If there is no file, ATARI_ERR_INVALID_VTOC_ENTRY is returned.
 */
AtariError AtariGetFileFromIndex(const char *szDisk, AtariFile *Info, long lIndex)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "rb")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * search from the first entry.
      */
     Info->lIndex = lIndex - 1;

     /*
      * fill Info structure.
      */
     iRet = AtariFindDirEntry(fd, Info);
     fclose(fd);

     /*
      * verify that we have found the entry expected by the user.
      */
     if ((iRet == ATARI_ERR_OK) && (Info->lIndex != lIndex))
          iRet = ATARI_ERR_INVALID_VTOC_ENTRY;
     return iRet;
}

/*
 * read first sector of a file (125 bytes).
 */
AtariError AtariReadFirstSector(const char *szDisk, AtariFile *Info)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "rb")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * reset Atari file pointer to begining of file
      */
     Info->wNextSector = Info->wStart;

     /*
      * fill user buffer.
      */
     iRet = AtariReadSector(fd, Info);
     fclose(fd);
     return iRet;
}

/*
 * read next sector of a file (125 bytes).
 */
AtariError AtariReadNextSector(const char *szDisk, AtariFile *Info)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "rb")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * fill user buffer.
      */
     iRet = AtariReadSector(fd, Info);
     fclose(fd);
     return iRet;
}

/*
 * checks that the file does not exist.
 */
AtariError AtariCheckFile(const char *szDisk, AtariFile *Info, const char *szFile)
{
AtariError iRet;

     /*
      * get first file name of the disk.
      */
     iRet = AtariFindFirst(szDisk, Info);
     while (iRet == ATARI_ERR_OK)
          {

          /*
           * check if the new file has the same name of one of
           * the files of the disk.
           */
          if (! stricmp(szFile, Info->szName))
               return ATARI_ERR_OK;

          /*
           * get the next file name.
           */
          iRet = AtariFindNext(szDisk, Info);
          }

     /*
      * returns the status to caller.
      */
     return iRet;
}

/*
 * creates a new empty file.
 */
AtariError AtariCreateFile(const char *szDisk, AtariFile *Info, const char *szFile)
{
FILE *fd;
AtariError iRet;

     /*
      * check if file already exist.
      */
     switch (iRet = AtariCheckFile(szDisk, Info, szFile))
          {
          case ATARI_ERR_OK:
               return ATARI_ERR_FILE_ALREADY_EXISTS;

          case ATARI_ERR_NO_ENTRY_FOUND:
               break;

          default:
               return iRet;
          } 

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "r+b")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * find a free directory entry.
      */
     if ((iRet = AtariGetFreeDirEntry(fd, &(Info->lIndex))) != ATARI_ERR_OK)
          {
          fclose(fd);
          return iRet;
          }

     /*
      * find a free sector for the first data sector of the file.
      */
     if ((iRet = AtariAllocSector(fd, &(Info->wStart), Info->lIndex)) != ATARI_ERR_OK)
          {
          fclose(fd);
          return iRet;
          }

     /*
      * fill this entry
      */
     strcpy(Info->szName, szFile);
     Info->wSize = 1;
     Info->wAttrib = ATARI_ATTR_IN_USE | ATARI_ATTR_UNKNOWN;
     Info->wNextSector = Info->wStart;

     /*
      * write entry into directory
      */
     if ((iRet = AtariWriteDirEntry(fd, Info)) != ATARI_ERR_OK)
          AtariFreeSector(fd, Info->wStart);
     fclose(fd);
     return iRet;
}

AtariError AtariWriteSector(const char *szDisk, AtariFile *Info)
{
FILE *fd;
AtariError iRet;

     /*
      * open Atari disk file (*.xfd).
      */
     if ((fd = fopen(szDisk, "r+b")) == NULL)
          return ATARI_ERR_DISK_NOT_FOUND;

     /*
      * find last sector where we can write the data.
      */
     if ((iRet = AtariSeekEndOfFile(fd, Info)) != ATARI_ERR_OK)
          {
          fclose(fd);
          return iRet;
          }

     /*
      * fill sector information
      */
     Info->szSector[125] = ((unsigned char) Info->lIndex & 0x3F) << 2;
     Info->szSector[126] = 0;
     Info->szSector[127] = Info->wSectorLng & 0x7F;

     /*
      * go to sector.
      */
     if (AtariSeek(fd, Info->wNextSector - 1))
          {
          fclose(fd);
          return ATARI_ERR_SECTOR_NOT_FOUND;
          }

     /*
      * the sector is written.
      */
     if (fwrite(Info->szSector, 128, 1, fd) != 1)
          iRet = ATARI_ERR_FILE_WRITE;
     fclose(fd);
     return iRet;
}
